﻿using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;

namespace Postnuclear.Collections
  {
  /// <summary>
  /// The extension methods for the <see cref="ObservableLinkedList{T}"/>.
  /// </summary>  
  public static partial class ExtensionMethods
    {
    #region Public

    // Methods

    /// <summary>
    /// Adds the specified new node at the specified index in the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    /// <param name="node">The new <see cref="ObservableLinkedList{T}.Node"/> to add at the start of the <see cref="ObservableLinkedList{T}"/>.</param>
    /// <param name="index">The index to insert node at.</param>
    [DebuggerHidden]
    public static void AddAt<T>(this ObservableLinkedList<T> source, ObservableLinkedList<T>.Node node, int index)
      {
      Contract.Requires<ArgumentNullException>(source != null);
      Contract.Requires<ArgumentNullException>(node != null);
      Contract.Requires<InvalidOperationException>(node.List == null);
      Contract.Requires<InvalidOperationException>(node.Next == null);
      Contract.Requires<InvalidOperationException>(node.Previous == null);
      Contract.Requires<InvalidOperationException>(index <= source.Count);

      Contract.Ensures(node.List == source);
      Contract.Ensures(node.Index == index);

      if (index == 0)
        {
        source.AddFirst(node);
        }
      else
        {
        source.AddAfter(source.Nodes.ElementAt(index - 1), node);
        }
      }

    /// <summary>
    /// Adds the specified new node at the specified index in the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    /// <param name="value">The value to add to the <see cref="ObservableLinkedList{T}"/>.</param>
    /// <param name="index">The index to insert node at.</param>
    /// <returns>The new <see cref="ObservableLinkedList{T}.Node"/> containing value.</returns>
    [DebuggerHidden]
    public static ObservableLinkedList<T>.Node AddAt<T>(this ObservableLinkedList<T> source, T value, int index)
      {
      Contract.Requires<ArgumentNullException>(source != null);
      Contract.Requires<InvalidOperationException>(index <= source.Count);

      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>() != null);
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().List == source);
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().Index == index);

      ObservableLinkedList<T>.Node node = new ObservableLinkedList<T>.Node(value);
      source.AddAt(node, index);

      return node;
      }

    /// <summary>
    /// Adds the specified new node after the specified existing node in the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    /// <param name="node">The <see cref="ObservableLinkedList{T}.Node"/> after which to insert newNode.</param>
    /// <param name="newNode">The new <see cref="ObservableLinkedList{T}.Node"/> to add to the <see cref="ObservableLinkedList{T}"/>.</param>
    [DebuggerHidden]
    public static void AddAfter<T>(this ObservableLinkedList<T> source, ObservableLinkedList<T>.Node node, ObservableLinkedList<T>.Node newNode)
      {
      Contract.Requires<ArgumentNullException>(source != null);
      Contract.Requires<ArgumentNullException>(node != null);
      Contract.Requires<InvalidOperationException>(node.List == source);
      Contract.Requires<ArgumentNullException>(newNode != null);
      Contract.Requires<InvalidOperationException>(newNode.List == null);
      Contract.Requires<InvalidOperationException>(newNode.Next == null);
      Contract.Requires<InvalidOperationException>(newNode.Previous == null);

      Contract.Ensures(newNode.List == source);
      Contract.Ensures(newNode.Previous == node);
      Contract.Ensures(newNode == node.Next);
      Contract.Ensures(newNode.Next == Contract.OldValue(node.Next));

      newNode.Previous = node;
      newNode.Next = node.Next;

      source.Add(newNode);
      }

    /// <summary>
    /// Adds a new node containing the specified value after the specified existing node in the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    /// <param name="node">The <see cref="ObservableLinkedList{T}.Node"/> after which to insert a new <see cref="ObservableLinkedList{T}.Node"/> containing value.</param>
    /// <param name="value">The value to add to the <see cref="ObservableLinkedList{T}"/>.</param>
    /// <returns>The new <see cref="ObservableLinkedList{T}.Node"/> containing value.</returns>
    [DebuggerHidden]
    public static ObservableLinkedList<T>.Node AddAfter<T>(this ObservableLinkedList<T> source, ObservableLinkedList<T>.Node node, T value)
      {
      Contract.Requires<ArgumentNullException>(source != null);
      Contract.Requires<ArgumentNullException>(node != null);
      Contract.Requires<InvalidOperationException>(node.List == source);

      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>() != null);
      Contract.Ensures(object.Equals(Contract.Result<ObservableLinkedList<T>.Node>().Value, value));
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().List == source);
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().Previous == node);
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>() == node.Next);
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().Next == Contract.OldValue(node.Next));

      ObservableLinkedList<T>.Node newNode = new ObservableLinkedList<T>.Node(value);
      source.AddAfter(node, newNode);

      return newNode;
      }

    /// <summary>
    /// Adds the specified new node before the specified existing node in the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    /// <param name="node">The <see cref="ObservableLinkedList{T}.Node"/> before which to insert newNode.</param>
    /// <param name="newNode">The new <see cref="ObservableLinkedList{T}.Node"/> to add to the <see cref="ObservableLinkedList{T}"/>.</param>
    [DebuggerHidden]
    public static void AddBefore<T>(this ObservableLinkedList<T> source, ObservableLinkedList<T>.Node node, ObservableLinkedList<T>.Node newNode)
      {
      Contract.Requires<ArgumentNullException>(source != null);
      Contract.Requires<ArgumentNullException>(node != null);
      Contract.Requires<InvalidOperationException>(node.List == source);
      Contract.Requires<ArgumentNullException>(newNode != null);
      Contract.Requires<InvalidOperationException>(newNode.List == null);
      Contract.Requires<InvalidOperationException>(newNode.Next == null);
      Contract.Requires<InvalidOperationException>(newNode.Previous == null);

      Contract.Ensures(newNode.List == source);
      Contract.Ensures(newNode.Next == node);
      Contract.Ensures(newNode == node.Previous);
      Contract.Ensures(newNode.Previous == Contract.OldValue(node.Previous));

      newNode.Next = node;
      newNode.Previous = node.Previous;

      source.Add(newNode);
      }

    /// <summary>
    /// Adds a new node containing the specified value before the specified existing node in the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    /// <param name="node">The <see cref="ObservableLinkedList{T}.Node"/> before which to insert a new <see cref="ObservableLinkedList{T}.Node"/> containing value.</param>
    /// <param name="value">The value to add to the <see cref="ObservableLinkedList{T}"/>.</param>
    /// <returns>The new <see cref="ObservableLinkedList{T}.Node"/> containing value.</returns>
    [DebuggerHidden]
    public static ObservableLinkedList<T>.Node AddBefore<T>(this ObservableLinkedList<T> source, ObservableLinkedList<T>.Node node, T value)
      {
      Contract.Requires<ArgumentNullException>(source != null);
      Contract.Requires<ArgumentNullException>(node != null);
      Contract.Requires<InvalidOperationException>(node.List == source);

      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>() != null);
      Contract.Ensures(object.Equals(Contract.Result<ObservableLinkedList<T>.Node>().Value, value));
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().List == source);
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().Next == node);
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>() == node.Previous);
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().Previous == Contract.OldValue(node.Previous));

      ObservableLinkedList<T>.Node newNode = new ObservableLinkedList<T>.Node(value);
      source.AddBefore(node, newNode);

      return newNode;
      }

    /// <summary>
    /// Adds the specified new node at the start of the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    /// <param name="node">The new <see cref="ObservableLinkedList{T}.Node"/> to add at the start of the <see cref="ObservableLinkedList{T}"/>.</param>
    [DebuggerHidden]
    public static void AddFirst<T>(this ObservableLinkedList<T> source, ObservableLinkedList<T>.Node node)
      {
      Contract.Requires<ArgumentNullException>(source != null);
      Contract.Requires<ArgumentNullException>(node != null);
      Contract.Requires<InvalidOperationException>(node.List == null);
      Contract.Requires<InvalidOperationException>(node.Next == null);
      Contract.Requires<InvalidOperationException>(node.Previous == null);

      Contract.Ensures(node.List == source);
      Contract.Ensures(node.Previous == null);
      Contract.Ensures(node.Next == Contract.OldValue(source.First));
      Contract.Ensures(node == source.First);

      if (source.First == null)
        {
        source.Add(node);
        }
      else
        {
        source.AddBefore(source.First, node);
        }
      }

    /// <summary>
    /// Adds a new node containing the specified value at the start of the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    /// <param name="value">The value to add at the start of the <see cref="ObservableLinkedList{T}"/>.</param>
    /// <returns>The new <see cref="ObservableLinkedList{T}.Node"/> containing value.</returns>
    [DebuggerHidden]
    public static ObservableLinkedList<T>.Node AddFirst<T>(this ObservableLinkedList<T> source, T value)
      {
      Contract.Requires<ArgumentNullException>(source != null);

      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>() != null);
      Contract.Ensures(object.Equals(Contract.Result<ObservableLinkedList<T>.Node>().Value, value));
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().List == source);
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().Previous == null);
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().Next == Contract.OldValue(source.First));
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>() == source.First);

      ObservableLinkedList<T>.Node newNode = new ObservableLinkedList<T>.Node(value);
      source.AddFirst(newNode);

      return newNode;
      }

    /// <summary>
    /// Adds the specified new node at the end of the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    /// <param name="node">The new <see cref="ObservableLinkedList{T}.Node"/> to add at the end of the <see cref="ObservableLinkedList{T}"/>.</param>
    [DebuggerHidden]
    public static void AddLast<T>(this ObservableLinkedList<T> source, ObservableLinkedList<T>.Node node)
      {
      Contract.Requires<ArgumentNullException>(source != null);
      Contract.Requires<ArgumentNullException>(node != null);
      Contract.Requires<InvalidOperationException>(node.List == null);
      Contract.Requires<InvalidOperationException>(node.Next == null);
      Contract.Requires<InvalidOperationException>(node.Previous == null);

      Contract.Ensures(node.List == source);
      Contract.Ensures(node.Next == null);
      Contract.Ensures(node.Previous == Contract.OldValue(source.Last));
      Contract.Ensures(node == source.Last);

      if (source.Last == null)
        {
        source.Add(node);
        }
      else
        {
        source.AddAfter(source.Last, node);
        }
      }

    /// <summary>
    /// Adds a new node containing the specified value at the end of the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    /// <param name="value">The value to add at the end of the <see cref="ObservableLinkedList{T}"/>.</param>
    /// <returns>The new <see cref="ObservableLinkedList{T}.Node"/> containing value.</returns>
    [DebuggerHidden]
    public static ObservableLinkedList<T>.Node AddLast<T>(this ObservableLinkedList<T> source, T value)
      {
      Contract.Requires<ArgumentNullException>(source != null);

      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>() != null);
      Contract.Ensures(object.Equals(Contract.Result<ObservableLinkedList<T>.Node>().Value, value));
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().List == source);
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().Next == null);
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>().Previous == Contract.OldValue(source.Last));
      Contract.Ensures(Contract.Result<ObservableLinkedList<T>.Node>() == source.Last);

      ObservableLinkedList<T>.Node newNode = new ObservableLinkedList<T>.Node(value);
      source.AddLast(newNode);

      return newNode;
      }

    /// <summary>
    /// Finds the first node that contains the specified value.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list to perform search in.</param>
    /// <param name="value">The value to locate in the <see cref="ObservableLinkedList{T}"/>.</param>
    /// <returns>
    /// The first <see cref="ObservableLinkedList{T}.Node"/> that contains the specified value, if found; otherwise, null.
    /// </returns>
    [DebuggerHidden]
    [Pure]
    public static ObservableLinkedList<T>.Node Find<T>(this ObservableLinkedList<T> source, T value)
      {
      Contract.Requires<ArgumentNullException>(source != null);

      Contract.Ensures(
        Contract.Result<ObservableLinkedList<T>.Node>() == null
        || object.Equals(Contract.Result<ObservableLinkedList<T>.Node>().Value, value));

      return source.Find(node => object.Equals(value, node));
      }

    /// <summary>
    /// Finds the first node that contains the specified value.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list to perform search in.</param>
    /// <param name="predicate">A function to test each element for a condition.</param>
    /// <returns>
    /// The first <see cref="ObservableLinkedList{T}.Node"/> that contains the suitable value, if found; otherwise, null.
    /// </returns>
    [DebuggerHidden]
    [Pure]
    public static ObservableLinkedList<T>.Node Find<T>(this ObservableLinkedList<T> source, Func<T, bool> predicate)
      {
      Contract.Requires<ArgumentNullException>(source != null);

      return source.Nodes.FirstOrDefault(node => predicate(node.Value));
      }

    /// <summary>
    /// Finds the last node that contains the specified value.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list to perform search in.</param>
    /// <param name="value">The value to locate in the <see cref="ObservableLinkedList{T}"/>.</param>
    /// <returns>
    /// The last <see cref="ObservableLinkedList{T}.Node"/> that contains the specified value, if found; otherwise, null.
    /// </returns>
    [DebuggerHidden]
    [Pure]
    public static ObservableLinkedList<T>.Node FindLast<T>(this ObservableLinkedList<T> source, T value)
      {
      Contract.Requires<ArgumentNullException>(source != null);

      Contract.Ensures(
        Contract.Result<ObservableLinkedList<T>.Node>() == null
        || object.Equals(Contract.Result<ObservableLinkedList<T>.Node>().Value, value));

      return source.FindLast(node => object.Equals(value, node));
      }

    /// <summary>
    /// Finds the last node that contains the specified value.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list to perform search in.</param>
    /// <param name="predicate">A function to test each element for a condition.</param>
    /// <returns>
    /// The last <see cref="ObservableLinkedList{T}.Node"/> that contains the specified value, if found; otherwise, null.
    /// </returns>
    [DebuggerHidden]
    [Pure]
    public static ObservableLinkedList<T>.Node FindLast<T>(this ObservableLinkedList<T> source, Func<T, bool> predicate)
      {
      Contract.Requires<ArgumentNullException>(source != null);

      return source.Nodes.Reverse().FirstOrDefault(node => predicate(node.Value));
      }

    /// <summary>
    /// ReRemoves the first occurrence of the specified value from the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    /// <param name="item">The value to remove from the <see cref="ObservableLinkedList{T}"/>.</param>
    /// <returns>
    /// <c>true</c> if the element containing value is successfully removed; otherwise, <c>false</c>.
    /// This method also returns <c>false</c> if value was not found in the original <see cref="ObservableLinkedList{T}"/>.
    /// </returns>
    [DebuggerHidden]
    public static bool Remove<T>(this ObservableLinkedList<T> source, T item)
      {
      Contract.Requires<InvalidOperationException>(source != null);

      ObservableLinkedList<T>.Node node = source.Find(item);

      if (node == null)
        {
        return false;
        }

      source.Remove(node);
      return true;
      }

    /// <summary>
    /// Removes the node at the start of the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    [DebuggerHidden]
    public static void RemoveFirst<T>(this ObservableLinkedList<T> source)
      {
      Contract.Requires<InvalidOperationException>(source != null);
      Contract.Requires<InvalidOperationException>(source.First != null);

      Contract.Ensures(Contract.OldValue<ObservableLinkedList<T>.Node>(source.First) != source.First);
      Contract.Ensures(Contract.OldValue<ObservableLinkedList<T>.Node>(source.First).List == null);
      Contract.Ensures(Contract.OldValue<ObservableLinkedList<T>.Node>(source.First).Next == null);
      Contract.Ensures(Contract.OldValue<ObservableLinkedList<T>.Node>(source.First).Previous == null);

      source.Remove(source.First);
      }

    /// <summary>
    /// Removes the node at the end of the <see cref="ObservableLinkedList{T}"/>.
    /// </summary>
    /// <typeparam name="T">Specifies the element type of the linked list.</typeparam>
    /// <param name="source">The source linked list.</param>
    [DebuggerHidden]
    public static void RemoveLast<T>(this ObservableLinkedList<T> source)
      {
      Contract.Requires<InvalidOperationException>(source != null);
      Contract.Requires<InvalidOperationException>(source.Last != null);

      Contract.Ensures(Contract.OldValue<ObservableLinkedList<T>.Node>(source.Last) != source.Last);
      Contract.Ensures(Contract.OldValue<ObservableLinkedList<T>.Node>(source.Last).List == null);
      Contract.Ensures(Contract.OldValue<ObservableLinkedList<T>.Node>(source.Last).Next == null);
      Contract.Ensures(Contract.OldValue<ObservableLinkedList<T>.Node>(source.Last).Previous == null);

      source.Remove(source.Last);
      }

    #endregion
    }
  }